package io.cloud.tec.ant_design_vue_pro.biz;

import io.cloud.tec.ant_design_vue_pro.dao.FreeServiceMapper;
import io.cloud.tec.ant_design_vue_pro.dto.FreeServiceRespDTO;
import io.cloud.tec.ant_design_vue_pro.dto.convert.FreeServiceConvert;
import io.cloud.tec.ant_design_vue_pro.entity.FreeService;
import io.cloud.tec.ant_design_vue_pro.enums.ServiceState;
import io.cloud.tec.ant_design_vue_pro.exception.OfflineException;
import io.cloud.tec.project.util.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.lang3.StringUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Service
public class FreeServiceService {

    FreeServiceMapper mapper;

    DefaultExecutor exec;

    @PostConstruct
    private void init() {
        log.info("init...");
        if (exec != null) {
            return;
        }
        //接收正常结果流
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        //接收异常结果流
        ByteArrayOutputStream errStream = new ByteArrayOutputStream();

        PumpStreamHandler streamHandler = new PumpStreamHandler(outStream, errStream);

        exec = new DefaultExecutor();
        exec.setStreamHandler(streamHandler);
    }

    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public List<FreeServiceRespDTO> list() {
        List<FreeService> list = mapper.selectFreeService();
        List<FreeServiceRespDTO> resp = FreeServiceConvert.INSTANCE.toFreeServiceRespDTOList(list);
        resp.forEach(s -> {
            final LocalDateTime now = LocalDateTime.now();
            final Period period = Period.between(s.getLastStateUpdateTime().toLocalDate(), now.toLocalDate());
            final Duration duration = Duration.between(s.getLastStateUpdateTime(), now);
            String extMsg = "从%s开始" + (ServiceState.Online.name().equals(s.getState()) ? "持续运行" : "已宕机") + "%d年%02d个月%03d天（%09d秒%03d纳秒）";
            s.setExt(String.format(extMsg, dateTimeFormatter.format(s.getLastStateUpdateTime())
                    , period.getYears()
                    , period.getMonths()
                    , period.getDays()
                    , duration.getSeconds()
                    , duration.getNano()
            ));
        });
        return resp;
    }

    //    @Scheduled(cron = "0 0/5 * * * ?")
    @Scheduled(initialDelay = 3, fixedRate = 100000000)
    private void validateLocalService() {
        List<FreeService> freeServiceList = mapper.selectFreeService();
        log.info("检测服务清单:{}个", freeServiceList.size());
        freeServiceList.forEach(s -> {
            StringJoiner message = new StringJoiner("\n");
            List<Integer> portList = new ArrayList<>();
            try {
                portList = resolvePort(s.getPort());
            } catch (Exception e) {
                message.add(new StringBuilder("解析端口失败:").append(s.getPort()));
            }

            String ip = StringUtils.isNotBlank(s.getCheckIp()) ? s.getCheckIp() : s.getIp();
            if (StringUtils.isBlank(ip)) {
                message.add("获取IP失败;");
            }
            FreeService updateParam = new FreeService();
            updateParam.setId(s.getId());
            try {
                message.add(new StringBuilder().append("ip:").append(ip).append(";ports:").append(JsonUtils.toJsonString(portList)).append(":\n"));
                singleIp(ip, portList, message);
                if (ServiceState.Offline.name().equals(s.getState())) {
                    updateParam.setState(ServiceState.Online.name());
                    updateParam.setLastStateUpdateTime(LocalDateTime.now());
                }
                online(message, updateParam);
            } catch (OfflineException offlineException) {
                offline(message, updateParam);
            }
        });
    }

    private void offline(StringJoiner message, FreeService updateParam) {
        updateParam.setState(ServiceState.Offline.name());
        updateParam.setLastStateUpdateTime(LocalDateTime.now());
        updateParam.setLastStateUpdateReason(message.toString());
        updateParam.setUpdateTime(LocalDateTime.now());
        mapper.update(updateParam);
    }

    private void online(StringJoiner message, FreeService updateParam) {
        updateParam.setLastStateUpdateReason(message.toString());
        updateParam.setUpdateTime(LocalDateTime.now());
        mapper.update(updateParam);
    }


    private void singleIp(String ip, List<Integer> portList, StringJoiner message) {
        AtomicBoolean serviceStatus = new AtomicBoolean(true);
        portList.forEach(p -> {
            final StringJoiner subMessage = new StringJoiner("\n");
            try {
                checkByProcessBuilder(ip, p, subMessage);
            } catch (OfflineException offlineException) {
                serviceStatus.compareAndSet(true, false);
            }
            message.merge(subMessage);
            if (!serviceStatus.get()) {
                throw new OfflineException("服务下线");
            }
        });
    }

    private void checkByProcessBuilder(String ip, Integer port, StringJoiner message) {
        final StringBuilder info = new StringBuilder("port:").append(port).append(";");
        ProcessBuilder pb = new ProcessBuilder("nc", "-z", ip, String.valueOf(port));
        Process process = null;
        try {
            final ProcessBuilder.Redirect redirectOutput = pb.redirectOutput();
            final ProcessBuilder.Redirect redirectError = pb.redirectError();
            process = pb.start();
            final int exitValue = process.waitFor();
//            info.append(redirectOutput.toString());
//            info.append(redirectError.toString());
            process.destroy();
            message.add(info.append("exit value:").append(exitValue));
        } catch (IOException | InterruptedException e) {
            message.add(info.append("exit:").append(e.getMessage()));
        }
    }

    private void checkByExec(String ip, Integer port, StringJoiner message) {
        final StringBuilder info = new StringBuilder("port:").append(port).append(";");
        CommandLine commandLine = CommandLine.parse("nc -z " + ip + " " + port);

        try {
            int exitValue = exec.execute(commandLine);
            message.add(info.append("exit value:").append(exitValue));
        } catch (IOException e) {
            message.add(info.append("exit:").append(e.getMessage()));
            throw new OfflineException(e.getMessage());
        }
    }

    private List<Integer> resolvePort(String port) {
        return Stream.of(port.split("/")).map(Integer::valueOf).collect(Collectors.toList());
    }
}
